Skip to content

我的流水线管家 GitHub Actions

在写代码的时候,无论是在公司还是自己写点东西,每次手里的代码写完了,本地环境也起来了,但是要部署到服务器上,得一个个命令敲,做多了不仅烦,而且还浪费时间,人肉 CI 真的难受

直到我接触了 GitHub Actions 一切都变得简单许多了,我再也不用一遍遍敲着哪些重复的命令,现在我把这些流程通过 yaml 文件,声明式的方式记录下来,“我的管家” GitHub Actions 就会帮我做,这多么棒啊!这样我就可以把时间和精力花在更重要的事情上了,因为重复枯燥,真的会把一个人给逼疯!

现在,公司项目用 GitLab Actions 部署测试/生产环境,而我个人的网站和 API 服务也是通过这个管家来管理自动构建发布,我把代码写完,推送到 Git 仓库之后,我的管家就开始为我干活了,生活变得美妙起来了

我不允许你还不知道这个东西,我将按下面的流程,将这个强大的管家介绍给你

  1. 一个简单的案例
  2. 构建并部署二进制文件到云服务器
  3. 构建并发布 Docker 镜像到镜像仓库
  4. 串起来!简单的云原生构建与发布流水线

一个简单的案例

首先,我们先来写一个最简单的 GitHub Actions 的 HelloWorld,先对它有个感性的认识

在开始之前,先把准备工作做好,创建一个空的 Git 仓库,然后在本地新建一个空文件夹。这一步我们暂时不写任何代码,直接在当前目录下创建文件:.github/workflows/helloworld.yaml

yaml
on: push

jobs:
  checkout-files:
    runs-on: ubuntu-latest
    steps:
      - run: pwd
      - run: ls
  check-node-version:
    runs-on: ubuntu-latest
    needs: checkout-files
    steps:
      - run: node -v
      - run: npm -v

这个 GitHub Actions workflow 在每次 push 时触发,包含两个 job:第一个 job 会列出当前工作目录和文件(pwdls),第二个 job 依赖于第一个,输出 node -vnpm -v,用于按顺序验证仓库内容与 Node/npm 运行环境

将本地仓库跟 Git 仓库关联,再 push 变更到 GitHub 之后,CI 就会自动触发

这里可以看到我们的 Helloworld 工作流执行成功,符合预期地输出了 node 和 npm 的版本号

构建并部署二进制文件到云服务器

在这个案例里,我们用 GitHub Actions 来实现 API 服务的全自动部署

流程大概是在 Actions 里 SSH 到服务器,编译并重启 systemd 服务。第一次部署的时候,需要先配置好 systemd 服务文件,后续只需推送代码即可自动完成构建、上传和重启

如果程序比较小,也可以直接在 Actions 中编译二进制文件并打包,再通过 SCP 上传到服务器,用 SSH 远程解压,最后重启 systemd 服务

.github/workflows/deploy.yml
yml
name: Deploy Go Web API

on:
  push:
    branches:
      - master

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version: "1.24.2"

      - name: Build app
        run: |
          GOOS=linux GOARCH=amd64 go build -o api-go main.go

      - name: Package binary
        run: |
          mkdir -p deploy
          mv api-go deploy/
          tar -czf api-go.tar.gz -C deploy api-go

      - name: Upload to server
        uses: appleboy/scp-action@v0.1.4
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          source: "api-go.tar.gz"
          target: "/root/bin/"

      - name: Remote restart service
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: |
            cd /root/bin
            tar -xzf api-go.tar.gz
            rm api-go.tar.gz
            sudo systemctl restart api-go.service

因为我们是用 systemd 来管理服务的,所以在第一次部署时,需要手动创建一个服务文件:/etc/systemd/system/api-go.service

ini
[Unit]
Description=vodka API
After=network.target

[Service]
User=root
Group=root

WorkingDirectory=/root/bin

ExecStart=/root/bin/api-go

# 程序挂了自动重启
Restart=always
RestartSec=5

Environment=PORT=8081

[Install]
WantedBy=multi-user.target

如果这是第一次部署应用,还需要额外配置一下 Nginx 反向代理。当然,这一步并不是必须的,只是为了让服务访问起来更方便和规范

conf
server {
    ...

    location /v3/ {
        proxy_pass http://127.0.0.1:8081/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Port 443;
    }
}

验证配置并重启 Nginx

sh
nginx -t
nginx -s reload

后续发布部署流程:

  1. 上传新的二进制,覆盖 /root/bin/api-go 编译后的可执行文件
  2. 执行:systemctl restart api-go.service 服务就会平滑更新,日志都能看见

当然,这些活儿你都不用自己动手,管家 会全程替你搞定。你要做的就是写好代码,然后推到 Git 仓库,剩下的交给自动化流程就行

CI 被触发后,等待工作流执行完成。当然,你也可以在工作流的最后增加邮件或消息通知,提示程序构建成功

接着,我们可以登录到服务器上,检查应用的运行状态:

sh
sudo systemctl status api-go

构建并发布 Docker 镜像到镜像仓库

在这个案例里,我们首先需要准备一个应用。为了演示方便,我选择了一个 Next.js 应用,但你完全可以根据需求,选择任何前端或后端应用,都可以套用同样的自动化部署流程

流程如下:

我们要构建镜像,首先要做的就是编写 Dockerfile

dockerfile
FROM node:20-alpine

WORKDIR /app

COPY package.json .

RUN npm install

COPY . .

RUN npm run build

CMD ["npm", "start"]

然后配置一下 GitHub Actions 的工作流配置

yaml
on: push

jobs:
  build-next:
    runs-on: ubuntu-latest
    steps:
      - name: checkout-code
        uses: actions/checkout@v4

      - name: login-docker-hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_TOKEN }}

      - name: build-push-image
        uses: docker/build-push-action@v5
        with:
          push: true
          tags: ${{ secrets.DOCKER_USERNAME }}/githubactions-docker:latest

在推送代码之前,我们需要先把 DockerHub 配置加到 GitHub Actions 的 Secrets 里:

  1. 获取 DockerHub AccessToken
  2. 创建 DockerHub 镜像仓库

工作流执行成功后,可以在本地拉取镜像,验证容器是否正常启动

串起来!简单的云原生构建与发布流水线

一般来说,云原生的构建发布流程是这么玩的:GitHub Actions 负责 CI 构建,ArgoCD 负责 CD 部署。至于 k8s 的部署文件,通常会放在一个独立的 Git 仓库里专门维护,这套玩法就叫 GitOps

简单理解,就是让 Git 驱动一切,让 ArgoCD 感知配置变化,驱动 k8s 自动滚动更新应用

准备 workflow 文件(仅供参考)
yaml
name: Rum API Workflow

on:
  push:
    branches: [master]

env:
  IMAGE_NAME: ccr.ccs.tencentyun.com/XXXX/rum-api

jobs:
  build-and-push:
    runs-on: ubuntu-latest

    outputs:
      tag: ${{ steps.bap.outputs.IMAGE_TAG }}

    steps:
      - name: Checkout Code
        uses: actions/checkout@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Tencent Cloud Registry
        run: echo "${{ secrets.CCR_PASSWORD }}" | docker login ccr.ccs.tencentyun.com --username=${{ secrets.CCR_USERNAME }} --password-stdin

      - name: Build and Push Image
        id: bap
        run: |
          TAG=$(date +%Y%m%d%H%M%S)
          docker build -t $IMAGE_NAME:$TAG .
          docker push $IMAGE_NAME:$TAG
          echo "IMAGE_TAG=$TAG" >> $GITHUB_OUTPUT

  deploy-to-k8s:
    runs-on: ubuntu-latest
    needs: build-and-push
    steps:
      - name: SSH into Server and Deploy
        env:
          IMAGE_TAG: ${{ needs.build-and-push.outputs.tag }}
        uses: appleboy/ssh-action@v1.0.0
        with:
          host: ${{ secrets.SERVER_IP }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: |
            export IMAGE_NAME=${{ env.IMAGE_NAME }}
            export IMAGE_TAG=${{ env.IMAGE_TAG }}
            cd /root/dev/rum-kustomize
            kustomize edit set image $IMAGE_NAME:$IMAGE_TAG
            kustomize build .
            kubectl apply -k .

为了方便教学演示,这里我先把 ArgoCD 拿掉。如果你在实际项目中用的是 ArgoCD,那就需要稍微改一下 deploy-to-k8s 中的 script 字段,主要逻辑是让它去同步 Git 仓库里的部署文件到最新版本。参考代码如下:

yaml
script:
  - git clone $REMOTE_URL
  - cd /root/dev/rum-kustomize
  - kustomize edit set image $IMAGE_NAME:$IMAGE_TAG
  - kustomize build .
  - git commit -am '[skip ci] staging kustomize update'
  - git push origin master

当你编写好自己的 Kubernetes 应用声明文件后,还需要准备一个 kustomization.yml 文件。因为我们会通过 kustomize 来对 k8s 配置进行版本化管理与动态更新,从而完成部署与发布

yaml
resources:
  - cm.yml
  - deploy.yml
  - svc.yml

images:
  - name: ccr.ccs.tencentyun.com/XXXX/rum-api
    newName: ccr.ccs.tencentyun.com/XXXX/rum-api
    newTag: "2025XXXXXX"
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

至此,整个流程就算完成啦。最后只需要提交代码,触发 CI,等构建流程跑完后,你的应用就会在集群中自动完成滚动更新

参考资料